package top.blackr.folivora.common.pay.pay;

import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.Status;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.Base64Util;
import net.dreamlu.mica.core.utils.FileUtil;
import net.dreamlu.mica.core.utils.JsonUtil;
import net.dreamlu.mica.core.utils.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import top.blackr.folivora.common.pay.exception.RefundException;
import top.blackr.folivora.common.pay.exception.PayException;
import top.blackr.folivora.common.pay.function.PayConsumer;
import top.blackr.folivora.common.pay.pay.entity.*;
import top.blackr.folivora.common.pay.utils.WxRobotUtil;
import top.blackr.folivora.config.ApplicationProperties;

import java.io.BufferedReader;
import java.io.File;
import java.math.BigDecimal;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信支付默认实现，可直接继承该类
 *
 * @author BlackR
 */
@Getter
@Slf4j
public abstract class DefaultWxJsapiPayStrategy<PR extends PayCallbackLog> implements IPayStrategy<WxOrderStrResult, Transaction, PR> {

    @Autowired
    private Config config;
    @Autowired
    private NotificationConfig notificationConfig;
    @Autowired
    private ApplicationProperties.WxPay wxProperties;

    @Override
    public WxOrderStrResult getOrderStr(OrderStrRequest request) {
        try {
            // 初始化服务
            JsapiServiceExtension service = new JsapiServiceExtension.Builder()
                    .config(config)
                    .signType("RSA") // 不填默认为RSA
                    .build();
            PrepayRequest prepayRequest = new PrepayRequest();
            Amount amount = new Amount();
            amount.setTotal(request.getTotalAmount().multiply(new BigDecimal("100")).intValue());
            prepayRequest.setAmount(amount);
            prepayRequest.setAppid(wxProperties.getMpAppId());
            prepayRequest.setMchid(wxProperties.getMchId());
            prepayRequest.setDescription(request.getTitle());
            prepayRequest.setNotifyUrl(request.getNotifyUrl());
            prepayRequest.setOutTradeNo(request.getOutTradeNo());
            Payer payer = new Payer();
            payer.setOpenid(request.getOpenId());
            prepayRequest.setPayer(payer);

            PrepayWithRequestPaymentResponse paymentResponse = service.prepayWithRequestPayment(prepayRequest);
            WxOrderStrResult result = new WxOrderStrResult();
            result.setSign(paymentResponse.getPaySign());
            result.setNonceStr(paymentResponse.getNonceStr());
            result.setSignType(paymentResponse.getSignType());
            result.setTimestamp(paymentResponse.getTimeStamp());
            result.setWxPackage(paymentResponse.getPackageVal());
            result.setAppId(paymentResponse.getAppId());
            return result;
        } catch (Exception e) {
            WxOrderStrResult result = new WxOrderStrResult();
            result.setErrorStr("微信创建订单失败：" + e.getMessage());
            return result;
        }
    }

    @SneakyThrows
    @Override
    public PR noticeCallback(HttpServletRequest request, Integer transactionType, PayConsumer<String, Transaction, PR> consumer) {
        Map<String, Object> map = new HashMap<>(4);
        PayCallbackLog payCallbackLog = getPayCallbackLog();
        try {
            BufferedReader br = request.getReader();
            String str;
            StringBuilder sb = new StringBuilder();
            while ((str = br.readLine()) != null) {
                sb.append(str);
            }
            // 构造 RequestParam
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(request.getHeader("Wechatpay-Serial"))
                    .nonce(request.getHeader("Wechatpay-Nonce"))
                    .signature(request.getHeader("Wechatpay-Signature"))
                    .timestamp(request.getHeader("Wechatpay-Timestamp"))
                    .body(String.valueOf(sb))
                    .build();
            if (requestParam.getSignature().contains("WECHATPAY/SIGNTEST")) {
                throw new RefundException("微信回调探测流量");
            }
            NotificationParser parser = new NotificationParser(notificationConfig);
            // 以支付通知回调为例，验签、解密并转换成 Transaction
            Transaction transaction = parser.parse(requestParam, Transaction.class);
            Transaction.TradeStateEnum tradeState = transaction.getTradeState();
            //支付状态交易状态，枚举值： SUCCESS：支付成功 REFUND：转入退款 NOTPAY：未支付 CLOSED：已关闭 REVOKED：已撤销（付款码支付）
            String outTradeNo = transaction.getOutTradeNo();
            payCallbackLog.setRetryNumber(0);
            payCallbackLog.setTradeNo(outTradeNo);
            payCallbackLog.setCreateTime(LocalDateTime.now());
            payCallbackLog.setTransactionType(transactionType);
            payCallbackLog.setCallbackStr(JsonUtil.toJson(transaction));
            payCallbackLog.setPayType(1);
            payCallbackLog.setTradeStatus(tradeState.name());

            if (tradeState.equals(Transaction.TradeStateEnum.SUCCESS)) {
                // 此处处理业务
                consumer.accept(outTradeNo, transaction, payCallbackLog);
                // 返回回调处理结果
                map.put("code", "SUCCESS");
                map.put("message", "成功");
                return resultLog(payCallbackLog, map, "", true);
            }
            map.put("code", "FAIL");
            map.put("message", "失败");
            return resultLog(payCallbackLog, map, "支付失败", false);
        } catch (Exception e) {
            log.error("微信支付回调失败-商品:", e);
            map.put("code", "FAIL");
            map.put("message", "失败");
            WxRobotUtil.sendException("wxPay", e);
            throw e;
        }
    }

    @Override
    public <ORR extends OrderRefundRequest> void refund(ORR refundRequest) {
        WxOrderRefundRequest wxRefundRequest = (WxOrderRefundRequest) refundRequest;
        Refund refund = null;
        try {
            AmountReq amountReq = new AmountReq();
            amountReq.setRefund(wxRefundRequest.getRefundAmount().multiply(new BigDecimal("100")).longValue());
            amountReq.setTotal(wxRefundRequest.getTotal().multiply(new BigDecimal("100")).longValue());
            amountReq.setCurrency("CNY");
            CreateRequest request = new CreateRequest();
            request.setOutTradeNo(wxRefundRequest.getTradeNo());
            request.setOutRefundNo(wxRefundRequest.getRefundTradeNo());
            request.setNotifyUrl(wxRefundRequest.getNotifyUrl());
            request.setAmount(amountReq);
            RefundService service = new RefundService.Builder().config(config).build();

            // 退款成功返回消息
            refund = service.create(request);
            if (refund == null) {
                throw new PayException("微信退款失败");
            } else {
                if (!refund.getStatus().equals(Status.SUCCESS) || !refund.getStatus().equals(Status.PROCESSING)) {
                    throw new PayException("微信退款失败");
                }
            }
        } catch (PayException e) {
            log.error("微信退款失败，退款信息:{}", JsonUtil.toJson(refund));
        } catch (Exception e) {
            log.error("微信退款失败", e);
            throw e;
        }
    }

    @SuppressWarnings("unchecked")
    public PR resultLog(PayCallbackLog payCallbackLog, Object result, String failReason, boolean noticeStatus) {
        payCallbackLog.setResult(result);
        payCallbackLog.setFailReason(failReason);
        payCallbackLog.setNoticeStatus(noticeStatus ? 0 : 1);
        return (PR) payCallbackLog;
    }


    public abstract PR getPayCallbackLog();

    @SneakyThrows
    public static void main(String[] args) {
        long timeStamp = Instant.now().getEpochSecond();
        String nonceStr = StringUtil.random(32);
        String packageStr = "prepay_id=wx20191126164107b0d0c0f0d0000000000";
        String data = "wx8d324d84bc2ed5bd\n"
                + timeStamp + "\n"
                + nonceStr + "\n"
                + packageStr + "\n";

        String read = FileUtil.readToString(new File("/Users/blackr/Desktop/夏丽智能/支付证书/wx/apiclient_key.pem"));
        read = read.replaceAll("-----BEGIN PRIVATE KEY-----", "")
                .replaceAll("-----END PRIVATE KEY-----", "").replaceAll("\n", "");

        byte[] decoded = Base64Util.decodeFromString(read);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes());
        byte[] signatureBytes = signature.sign();

        String paySign = Base64Util.encodeToString(signatureBytes);

        Map<String, Object> result = new HashMap<>();
        result.put("timeStamp", timeStamp);
        result.put("nonceStr", nonceStr);
        result.put("package", packageStr);
        result.put("paySign", paySign);
        result.put("signType", "RSA");
        System.out.println(JsonUtil.toJson(result));
    }
}
